package cn.zhaosunny.soap.internal

import cn.zhaosunny.soap.Soap
import cn.zhaosunny.soap.SoapRequest
import cn.zhaosunny.soap.exception.SoapException
import cn.zhaosunny.soap.exception.SoapHttpException
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.internal.closeQuietly
import org.ksoap2.SoapEnvelope
import org.ksoap2.SoapFault
import org.ksoap2.serialization.SoapObject
import org.ksoap2.serialization.SoapPrimitive
import org.ksoap2.serialization.SoapSerializationEnvelope
import org.kxml2.io.KXmlParser
import org.kxml2.io.KXmlSerializer
import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlSerializer
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.lang.reflect.Method
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.lang.reflect.WildcardType
import java.util.*
import kotlin.coroutines.Continuation

/**
 *
 * @author zhaoyang 2021/11/30
 */

object SoapHttp {
    private val prefixes: HashMap<*, *> = HashMap<Any?, Any?>()

    fun getPrefixes(): HashMap<*, *>? {
        return prefixes
    }

    private val xmlVersionTag = ""
    private val CONTENT_TYPE_XML_CHARSET_UTF_8 = "text/xml;charset=utf-8"
    private val CONTENT_TYPE_SOAP_XML_CHARSET_UTF_8 = "application/soap+xml;charset=utf-8"
    fun call(soapRequest: SoapRequest): String {
        return ""
    }

    fun call(
        soap: Soap,
        nameSpace: String,
        methodName: String,
        endPoint: String,
        params: LinkedHashMap<String, Any>,
    ): String {
        // 指定WebService的命名空间和调用的方法名
        val soapObject = SoapObject(nameSpace, methodName)
        params.asIterable().forEach {
            soapObject.addProperty(it.key, it.value)
        }
        // 生成调用WebService方法的SOAP请求信息,并指定SOAP的版本
        val envelope = SoapSerializationEnvelope(SoapEnvelope.VER11)
        envelope.bodyOut = soapObject
        // 设置是否调用的是dotNet开发的WebService
        envelope.dotNet = false
        val soapAction = if (soap.isNewForJDK8) {
            "\"\""
        } else nameSpace + methodName

        val requestBuilder = Request.Builder()
        if (envelope.version != SoapSerializationEnvelope.VER12) {
            requestBuilder.addHeader("SOAPAction", soapAction)
        }
        if (envelope.version == SoapSerializationEnvelope.VER12) {
            requestBuilder.addHeader("Content-Type", CONTENT_TYPE_SOAP_XML_CHARSET_UTF_8)
        } else {
            requestBuilder.addHeader("Content-Type", CONTENT_TYPE_XML_CHARSET_UTF_8)
        }

        requestBuilder.addHeader("Accept-Encoding", "gzip")
        val requestData: ByteArray? = createRequestData(envelope, "UTF-8")
        val requestBody = requestData!!.toRequestBody()

        val postRequest = requestBuilder
            .url(soap.baseUrl + endPoint)
            .post(requestBody).build()

        val response = soap.okHttpClient.newCall(postRequest).execute()

        if (!response.isSuccessful && response.code != 500) {
            soap.interceptor?.log(nameSpace,
                methodName,
                endPoint,
                params,
                response.body?.string() ?: "")
            throw SoapHttpException(response)
        }
        try {
            val xp: XmlPullParser = KXmlParser()
            xp.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true)
            xp.setInput(response.body!!.byteStream(), null)
            envelope.parse(xp)
            response.body!!.byteStream().closeQuietly()
            val envelopResponse = envelope.response
            if (envelopResponse != null && envelopResponse is SoapPrimitive) {
                val result = envelopResponse.value.toString()
                if (soap.errorStateService!!.isError(result)) {
                    val errorInfo = soap.errorStateService!!.getErrorInfo(result)
                    soap.interceptor?.log(
                        nameSpace,
                        methodName,
                        endPoint,
                        params, errorInfo)
                    throw SoapException(errorInfo)
                } else {
                    soap.interceptor?.log(nameSpace,
                        methodName,
                        endPoint,
                        params, result)
                    return result
                }
            } else {
                throw SoapException("数据查询为空")
            }
        } catch (e: SoapFault) {
            soap.interceptor?.log(nameSpace,
                methodName,
                endPoint,
                params, e.toString())
            throw SoapException("请求失败：" + e.faultstring)
        }
    }

    fun call(soap: Soap, soapRequest: SoapRequest): String {
        return call(soap,
            soapRequest.nameSpace!!,
            soapRequest.methodName!!,
            soapRequest.endPoint!!,
            soapRequest.params)
    }


    @Throws(IOException::class)
    open fun createRequestData(envelope: SoapEnvelope, encoding: String?): ByteArray? {
        var bos: ByteArrayOutputStream? = ByteArrayOutputStream()
        var result: ByteArray? = null
        bos?.write(xmlVersionTag.toByteArray())
        var xw: XmlSerializer? = KXmlSerializer()
        val keysIter: Iterator<*> = prefixes.keys.iterator()
        xw!!.setOutput(bos, encoding)
        while (keysIter.hasNext()) {
            val key = keysIter.next() as String
            xw.setPrefix(key, prefixes.get(key) as String)
        }
        envelope.write(xw)
        xw.flush()
        bos!!.write('\r'.code)
        bos.write('\n'.code)
        bos.flush()
        result = bos.toByteArray()
        xw = null
        bos = null
        return result
    }

    /**
     * Serializes the request.
     */
    @Throws(IOException::class)
    open fun createRequestData(envelope: SoapEnvelope): ByteArray? {
        return createRequestData(envelope, null)
    }


    fun getContinuation(method: Method): Continuation<*> {
        return method.genericParameterTypes.last() as Continuation<*>
    }

    fun getKotinRawType(method: Method): Type {
        return ((method.genericParameterTypes.last() as ParameterizedType)
            .actualTypeArguments[0] as WildcardType).lowerBounds[0]
    }
}